/**
 * 
 */
package qy.jalgotrade.broker.fillstrategy;

import java.util.Arrays;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.broker.Broker;
import qy.jalgotrade.broker.Order;

/**
 * Base class for order filling strategies for the backtester.
 * 
 * @author c-geo
 *
 */
public abstract class FillStrategy {

	/**
	 * Returns the trigger price for a Limit or StopLimit order, or None if the limit price was not
	 * yet penetrated.
	 * 
	 * @param action
	 * @param limitPrice
	 * @param useAdjustedValues
	 * @param bar
	 * @return
	 */
	public static double getLimitPriceTrigger(Order.Action action, double limitPrice, boolean useAdjustedValues,
			Bar bar) {

		double ret = Double.NaN;
		double open = bar.getOpen(useAdjustedValues);
		double high = bar.getHigh(useAdjustedValues);
		double low = bar.getLow(useAdjustedValues);

		/*
		 * If the bar is below the limit price, use the open price. If the bar includes the limit
		 * price, use the open price or the limit price.
		 */
		if (Arrays.asList(Order.Action.BUY, Order.Action.BUY_TO_COVER).contains(action)) {
			if (high < limitPrice) {
				ret = open;
			} else if (limitPrice >= low) {
				if (open < limitPrice) { // The limit price was penetrated on open.
					ret = open;
				} else {
					ret = limitPrice;
				}
			}
			/*
			 * If the bar is above the limit price, use the open price. If the bar includes the
			 * limit price, use the open price or the limit price.
			 */
		} else if (Arrays.asList(Order.Action.SELL, Order.Action.SELL_SHORT).contains(action)) {
			if (low > limitPrice) {
				ret = open;
			} else if (limitPrice <= high) {
				if (open > limitPrice) { // The limit price was penetrated on open.
					ret = open;
				} else {
					ret = limitPrice;
				}
			}
		} else { // Unknown action
			assert false;
		}

		return ret;
	}

	/**
	 * Returns the trigger price for a Stop or StopLimit order, or None if the stop price was not
	 * yet penetrated.
	 * 
	 * @param action
	 * @param stopPrice
	 * @param useAdjustedValues
	 * @param bar
	 * @return
	 */
	public static double getStopPriceTrigger(Order.Action action, double stopPrice, boolean useAdjustedValues,
			Bar bar) {

		double ret = Double.NaN;
		double open = bar.getOpen(useAdjustedValues);
		double high = bar.getHigh(useAdjustedValues);
		double low = bar.getLow(useAdjustedValues);

		/*
		 * If the bar is above the stop price, use the open price. If the bar includes the stop
		 * price, use the open price or the stop price. Whichever is better.
		 */
		if (Arrays.asList(Order.Action.BUY, Order.Action.BUY_TO_COVER).contains(action)) {
			if (low > stopPrice) {
				ret = open;
			} else if (stopPrice <= high) {
				if (open > stopPrice) { // The stop price was penetrated on open.
					ret = open;
				} else {
					ret = stopPrice;
				}
			}
			/*
			 * If the bar is below the stop price, use the open price. If the bar includes the stop
			 * price, use the open price or the stop price. Whichever is better.
			 */
		} else if (Arrays.asList(Order.Action.SELL, Order.Action.SELL_SHORT).contains(action)) {
			if (high < stopPrice) {
				ret = open;
			} else if (stopPrice >= low) {
				if (open < stopPrice) { // The stop price was penetrated on open.
					ret = open;
				} else {
					ret = stopPrice;
				}
			}
		} else { // Unknown action
			assert false;
		}

		return ret;
	}

	/**
	 * 
	 */
	public FillStrategy() {

	}

	/**
	 * Override (optional) to get notified when the broker is about to process new bars.
	 * 
	 * @param broker
	 *            The broker.
	 * @param bars
	 *            The current bars.
	 */
	public void onBars(Broker broker, Bars bars) {

	}

	/**
	 * Override (optional) to get notified when an order was filled, or partially filled.
	 * 
	 * @param broker
	 *            The broker.
	 * @param order
	 *            The order filled.
	 */
	public void onOrderFilled(Broker broker, Order order) {

	}

	/**
	 * Override to return the fill price and quantity for a market order or None if the order can't
	 * be filled at the given time.
	 * 
	 * @param broker
	 *            The broker.
	 * @param order
	 *            The order.
	 * @param bar
	 *            The current bar.
	 * @return A :class:`FillInfo` or None if the order should not be filled.
	 */
	public abstract FillInfo fillMarketOrder(Broker broker, Order order, Bar bar);

	/**
	 * Override to return the fill price and quantity for a limit order or None if the order can't
	 * be filled at the given time.
	 * 
	 * @param broker
	 *            The broker.
	 * @param order
	 *            The order.
	 * @param bar
	 *            The current bar.
	 * @return A :class:`FillInfo` or None if the order should not be filled.
	 */
	public abstract FillInfo fillLimitOrder(Broker broker, Order order, Bar bar);

	/**
	 * Override to return the fill price and quantity for a stop order or None if the order can't be
	 * filled at the given time.
	 * 
	 * @param broker
	 *            The broker.
	 * @param order
	 *            The order.
	 * @param bar
	 *            The current bar.
	 * @return A :class:`FillInfo` or None if the order should not be filled.
	 */
	public abstract FillInfo fillStopOrder(Broker broker, Order order, Bar bar);

	/**
	 * Override to return the fill price and quantity for a stop limit order or None if the order
	 * can't be filled at the given time.
	 * 
	 * @param broker
	 *            The broker.
	 * @param order
	 *            The order.
	 * @param bar
	 *            The current bar.
	 * @return A :class:`FillInfo` or None if the order should not be filled.
	 */
	public abstract FillInfo fillStopLimitOrder(Broker broker, Order order, Bar bar);
}
